package com.highper.common.sehcma;

import com.alibaba.druid.sql.ast.*;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement;
import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Properties;

/**
 * @Author ZhangGJ
 * @Date 2022/01/24 15:14
 */
@Slf4j
@Intercepts(value = {
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})}
)
public class MultiTenantInterceptor implements Interceptor {

    private static final String SCHEMA_NAME_PREFIX = "erp_";

    private static final String DEFAULT_SCHEMA_NAME = "common";

    private String schemaName;

    private String databaseType;

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    public void setSchemaName(String schemaName) {
        this.schemaName = schemaName;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        schemaName = SCHEMA_NAME_PREFIX + getCurrentSchemaName(mappedStatement);
        args[0] = getNewMappedStatement(parameter, mappedStatement);
        return invocation.proceed();
    }

    private String getCurrentSchemaName(MappedStatement mappedStatement) {
        return DEFAULT_SCHEMA_NAME;
    }

    public String processSqlByInterceptor(String sql) {
        String databaseType = System.getenv("database.type");
        if (StringUtils.isEmpty(databaseType)) {
            databaseType = System.getProperty("database.type");
            if (StringUtils.isEmpty(databaseType)) {
                databaseType = "mysql";
            }
        }
        SQLStatementParser parser = null;
        if ("mysql".equals(databaseType)) {
            parser = new MySqlStatementParser(sql);
        } else if ("oracle".equals(databaseType)) {
            parser = new OracleStatementParser(sql);
        } else {
            throw new RuntimeException("Unknown database type");
        }
        SQLStatement sqlStatement = parser.parseStatement();
        if (sqlStatement instanceof SQLTruncateStatement) {
            SQLTruncateStatement sqlTruncateStatement = (SQLTruncateStatement) sqlStatement;
            List<SQLExprTableSource> tableSources = sqlTruncateStatement.getTableSources();
            for (SQLExprTableSource sqlExprTableSource : tableSources) {
                setSQLSchema(sqlExprTableSource);
            }
        }
        if (sqlStatement instanceof SQLSelectStatement) {
            SQLSelectStatement sqlSelectStatement = (SQLSelectStatement) sqlStatement;
            SQLSelectQuery sqlSelectQuery = sqlSelectStatement.getSelect().getQuery();
            setSQLSchema(sqlSelectQuery);
        }
        if (sqlStatement instanceof SQLUpdateStatement) {
            SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatement;
            SQLTableSource sqlTableSource = sqlUpdateStatement.getTableSource();
            setSQLSchema(sqlTableSource);
            SQLExpr where = sqlUpdateStatement.getWhere();
            setSQLSchema(where);
        }
        if (sqlStatement instanceof SQLInsertStatement) {
            SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) sqlStatement;
            SQLExprTableSource tableSource = sqlInsertStatement.getTableSource();
            List<SQLInsertStatement.ValuesClause> children = sqlInsertStatement.getValuesList();
            if (children.size() != 0) {
                for (SQLInsertStatement.ValuesClause valuesClause : children) {
                    List<SQLExpr> values = valuesClause.getValues();
                    for (SQLExpr sqlExpr : values) {
                        if (sqlExpr instanceof SQLQueryExpr) {
                            setSQLSchema(sqlExpr);
                        }
                    }
                }
            }
            setSQLSchema(tableSource);
        }
        if (sqlStatement instanceof SQLDeleteStatement) {
            SQLDeleteStatement sqlDeleteStatement = (SQLDeleteStatement) sqlStatement;
            SQLTableSource tableSource = sqlDeleteStatement.getTableSource();
            setSQLSchema(tableSource);
            SQLExpr where = sqlDeleteStatement.getWhere();
            setSQLSchema(where);
        }
        if (sqlStatement instanceof SQLCreateStatement) {
            SQLCreateTableStatement sqlCreateStatement = (SQLCreateTableStatement) sqlStatement;
            SQLExprTableSource tableSource = sqlCreateStatement.getTableSource();
            setSQLSchema(tableSource);
        }
        if (sqlStatement instanceof SQLCallStatement) {
            log.info("执行到 函数 这里了");
            SQLCallStatement sqlCallStatement = (SQLCallStatement) sqlStatement;
            SQLName expr = sqlCallStatement.getProcedureName();
            if (expr instanceof SQLIdentifierExpr) {
                SQLIdentifierExpr procedureName = (SQLIdentifierExpr) expr;
                sqlCallStatement.setProcedureName(new SQLPropertyExpr(schemaName, procedureName.getName()));
            } else if (expr instanceof SQLPropertyExpr) {
                SQLPropertyExpr procedureName = (SQLPropertyExpr) expr;
                sqlCallStatement.setProcedureName(new SQLPropertyExpr(schemaName, procedureName.getName()));
            }
        }
        if (sqlStatement instanceof SQLMergeStatement) {
            SQLMergeStatement sqlMergeStatement = (SQLMergeStatement) sqlStatement;
            setSQLSchema(sqlMergeStatement.getInto());
            SQLTableSource sqlTableSource = sqlMergeStatement.getUsing();
            setSQLSchema(sqlTableSource);
            SQLMergeStatement.MergeUpdateClause updateClause = sqlMergeStatement.getUpdateClause();
            List<SQLUpdateSetItem> items = updateClause.getItems();
            for (SQLUpdateSetItem sqlUpdateSetItem : items) {
                setSQLSchema(sqlUpdateSetItem.getValue());
            }
        }
        return sqlStatement.toString();
    }

    private MappedStatement getNewMappedStatement(Object parameter, MappedStatement mappedStatement) {
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        log.debug("原SQL：{}", boundSql.getSql());
        String resultSql = processSqlByInterceptor(boundSql.getSql());
        log.debug("结果SQL：{}", resultSql);
        BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), resultSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
        MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), parameterObject -> newBoundSql, mappedStatement.getSqlCommandType());
        builder.resource(mappedStatement.getResource());
        builder.fetchSize(mappedStatement.getFetchSize());
        builder.statementType(mappedStatement.getStatementType());
        builder.keyGenerator(mappedStatement.getKeyGenerator());
        if (mappedStatement.getKeyProperties() != null && mappedStatement.getKeyProperties().length > 0) {
            builder.keyProperty(mappedStatement.getKeyProperties()[0]);
        }
        builder.timeout(mappedStatement.getTimeout());
        builder.parameterMap(mappedStatement.getParameterMap());
        builder.resultMaps(mappedStatement.getResultMaps());
        builder.resultSetType(mappedStatement.getResultSetType());
        builder.cache(mappedStatement.getCache());
        builder.flushCacheRequired(mappedStatement.isFlushCacheRequired());
        builder.useCache(mappedStatement.isUseCache());
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        return builder.build();
    }

    private void setSQLSchema(SQLSelectQuery sqlSelectQuery) {
        if (sqlSelectQuery instanceof SQLUnionQuery) {
            SQLUnionQuery sqlUnionQuery = (SQLUnionQuery) sqlSelectQuery;
            SQLSelectQuery sqlSelectQueryLeft = sqlUnionQuery.getLeft();
            setSQLSchema(sqlSelectQueryLeft);
            SQLSelectQuery sqlSelectQueryRight = sqlUnionQuery.getRight();
            setSQLSchema(sqlSelectQueryRight);
        }
        if (sqlSelectQuery instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock sqlSelectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery;
            SQLTableSource sqlTableSource = sqlSelectQueryBlock.getFrom();
            if (!("oracle".equals(databaseType) && "dual".equals(sqlTableSource.toString()))) {
                setSQLSchema(sqlTableSource);
            }
            SQLExpr whereSqlExpr = sqlSelectQueryBlock.getWhere();
            if (whereSqlExpr instanceof SQLInSubQueryExpr) {
                SQLInSubQueryExpr sqlInSubQueryExpr = (SQLInSubQueryExpr) whereSqlExpr;
                SQLSelectQuery sqlSelectQueryIn = sqlInSubQueryExpr.getSubQuery().getQuery();
                setSQLSchema(sqlSelectQueryIn);
            }
            if (whereSqlExpr instanceof SQLExistsExpr) {
                SQLExistsExpr sqlExistsExpr = (SQLExistsExpr) whereSqlExpr;
                SQLSelectQuery sqlSelectQueryIn = sqlExistsExpr.getSubQuery().getQuery();
                setSQLSchema(sqlSelectQueryIn);
            }
            if (whereSqlExpr instanceof SQLBinaryOpExpr) {
                SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) whereSqlExpr;
                setSQLSchema(sqlBinaryOpExpr);
            }
            if (whereSqlExpr instanceof SQLMethodInvokeExpr) {
                SQLMethodInvokeExpr sqlExpr = (SQLMethodInvokeExpr) whereSqlExpr;
                setSQLSchema(sqlExpr);
            }
            //如果order by后面有子查询，则增加order by处理
            //------------开始------------
            SQLOrderBy orderBy = sqlSelectQueryBlock.getOrderBy();
            if (orderBy!=null&&orderBy.getItems()!=null){
                for (SQLSelectOrderByItem item : orderBy.getItems()) {
                    SQLExpr sqlExpr = item.getExpr();
                    setSQLSchema(sqlExpr);
                    if (sqlExpr instanceof SQLMethodInvokeExpr) {
                        if (sqlExpr.toString().contains("hifm_function_") || sqlExpr.toString()
                                .contains("hifm_procedure_")) {
                            ((SQLMethodInvokeExpr) sqlExpr).setOwner(new SQLIdentifierExpr(schemaName));
                        }
                        //                    if (sqlSelectQuery instanceof SQLSelectQueryBlock && ((SQLSelectQueryBlock) sqlSelectQuery).getFrom() == null) {
                        //                        ((SQLMethodInvokeExpr) sqlExpr).setOwner(new SQLIdentifierExpr(schemaName));
                        //                    }
                    }
                }
            }
            //------------结束------------
            List<SQLSelectItem> sqlSelectItemList = sqlSelectQueryBlock.getSelectList();
            for (SQLSelectItem sqlSelectItem : sqlSelectItemList) {
                SQLExpr sqlExpr = sqlSelectItem.getExpr();
                //                if (sqlExpr instanceof SQLAggregateExpr) {
                //                    ((SQLAggregateExpr) sqlExpr).getArguments().var
                //                    continue;
                //                }
                setSQLSchema(sqlExpr);
                if (sqlExpr instanceof SQLMethodInvokeExpr) {
                    String methodName = ((SQLMethodInvokeExpr) sqlExpr).getMethodName();
                    if (methodName.contains("hifm_function_") || methodName.contains("hifm_procedure_")) {
                        ((SQLMethodInvokeExpr) sqlExpr).setOwner(new SQLIdentifierExpr(schemaName));
                    }
                    //                    if (sqlSelectQuery instanceof SQLSelectQueryBlock && ((SQLSelectQueryBlock) sqlSelectQuery).getFrom() == null) {
                    //                        ((SQLMethodInvokeExpr) sqlExpr).setOwner(new SQLIdentifierExpr(schemaName));
                    //                    }
                }
            }
        }
    }

    private void setSQLSchema(SQLTableSource sqlTableSource) {
        if (sqlTableSource instanceof SQLJoinTableSource) {
            SQLJoinTableSource sqlJoinTableSource = (SQLJoinTableSource) sqlTableSource;
            SQLTableSource sqlTableSourceLeft = sqlJoinTableSource.getLeft();
            setSQLSchema(sqlTableSourceLeft);
            SQLTableSource sqlTableSourceRight = sqlJoinTableSource.getRight();
            setSQLSchema(sqlTableSourceRight);
            SQLExpr condition = sqlJoinTableSource.getCondition();
            setSQLSchema(condition);
        }
        if (sqlTableSource instanceof SQLSubqueryTableSource) {
            SQLSubqueryTableSource sqlSubqueryTableSource = (SQLSubqueryTableSource) sqlTableSource;
            SQLSelectQuery sqlSelectQuery = sqlSubqueryTableSource.getSelect().getQuery();
            setSQLSchema(sqlSelectQuery);
        }
        if (sqlTableSource instanceof SQLUnionQueryTableSource) {
            SQLUnionQueryTableSource sqlUnionQueryTableSource = (SQLUnionQueryTableSource) sqlTableSource;
            SQLSelectQuery sqlSelectQueryLeft = sqlUnionQueryTableSource.getUnion().getLeft();
            setSQLSchema(sqlSelectQueryLeft);
            SQLSelectQuery sqlSelectQueryRight = sqlUnionQueryTableSource.getUnion().getRight();
            setSQLSchema(sqlSelectQueryRight);
        }
        if (sqlTableSource instanceof SQLExprTableSource) {
            SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) sqlTableSource;
            if (!sqlExprTableSource.toString().contains("mysql.") && !sqlExprTableSource.toString().contains("dual")) {
                SQLObject sqlObject = sqlExprTableSource.getParent();
                if (sqlObject instanceof MySqlDeleteStatement) {
                    MySqlDeleteStatement mySqlDeleteStatement = (MySqlDeleteStatement) sqlObject;
                    SQLExpr sqlExpr = mySqlDeleteStatement.getWhere();
                    setSQLSchema(sqlExpr);
                }
                if (sqlObject instanceof MySqlInsertStatement) {
                    MySqlInsertStatement mySqlInsertStatement = (MySqlInsertStatement) sqlObject;
                    SQLSelect sqlSelect = mySqlInsertStatement.getQuery();
                    if (sqlSelect != null) {
                        SQLSelectQuery sqlSelectQuery = sqlSelect.getQuery();
                        setSQLSchema(sqlSelectQuery);
                    }
                }
                // OF_FIND_IN_SET 非正常表 无法识别
                if (sqlExprTableSource.toString().contains("OF_STR_TO_LIST")) {
                    sqlExprTableSource.setExpr(schemaName+'.'+sqlExprTableSource.toString());
                    sqlExprTableSource.setAlias("");
                }else {
                    sqlExprTableSource.setSchema(schemaName);
                }
            }
        }
        if (sqlTableSource instanceof OracleSelectTableReference) {
            OracleSelectTableReference oracleSelectTableReference = (OracleSelectTableReference) sqlTableSource;
            SQLObject sqlObject = oracleSelectTableReference.getParent();
            if (sqlObject instanceof OracleUpdateStatement) {
                OracleUpdateStatement updateStatement = (OracleUpdateStatement) sqlObject;
                List<SQLUpdateSetItem> items = updateStatement.getItems();
                for (SQLUpdateSetItem sqlUpdateSetItem : items) {
                    SQLExpr sqlExpr = sqlUpdateSetItem.getValue();
                    setSQLSchema(sqlExpr);
                }
            }
        }
    }

    private void setSQLSchema(SQLBinaryOpExpr sqlBinaryOpExpr) {
        SQLExpr sqlExprLeft = sqlBinaryOpExpr.getLeft();
        setSQLSchema(sqlExprLeft);
        SQLExpr sqlExprRight = sqlBinaryOpExpr.getRight();
        setSQLSchema(sqlExprRight);
    }

    private void setSQLSchema(SQLExpr sqlExpr) {
        if (sqlExpr instanceof SQLAggregateExpr) {
            SQLAggregateExpr sqlAggregateExpr = (SQLAggregateExpr) sqlExpr;
            List<SQLExpr> arguments = sqlAggregateExpr.getArguments();
            for (SQLExpr argument : arguments) {
                setSQLSchema(argument);
            }
        }
        if (sqlExpr instanceof SQLMethodInvokeExpr) {
            String sqlString = ((SQLMethodInvokeExpr) sqlExpr).getMethodName();
            if (sqlString.startsWith("OF_") || sqlString.startsWith("OP_")) {
                ((SQLMethodInvokeExpr) sqlExpr).setOwner(new SQLIdentifierExpr(schemaName));
            }
        }
        if (sqlExpr instanceof SQLInSubQueryExpr) {
            SQLInSubQueryExpr sqlInSubQueryExpr = (SQLInSubQueryExpr) sqlExpr;
            SQLSelectQuery sqlSelectQuery = sqlInSubQueryExpr.getSubQuery().getQuery();
            setSQLSchema(sqlSelectQuery);
        }
        if (sqlExpr instanceof SQLExistsExpr) {
            SQLExistsExpr sqlExistsExpr = (SQLExistsExpr) sqlExpr;
            SQLSelectQuery sqlSelectQuery = sqlExistsExpr.getSubQuery().getQuery();
            setSQLSchema(sqlSelectQuery);
        }
        if (sqlExpr instanceof SQLCaseExpr) {
            SQLCaseExpr sqlCaseExpr = (SQLCaseExpr) sqlExpr;
            List<SQLCaseExpr.Item> sqlCaseExprItemList = sqlCaseExpr.getItems();
            for (SQLCaseExpr.Item item : sqlCaseExprItemList) {
                SQLExpr conditionExpr = item.getConditionExpr();
                setSQLSchema(conditionExpr);
                SQLExpr sqlExprItem = item.getValueExpr();
                setSQLSchema(sqlExprItem);
            }
        }
        if (sqlExpr instanceof SQLQueryExpr) {
            SQLQueryExpr sqlQueryExpr = (SQLQueryExpr) sqlExpr;
            SQLSelectQuery sqlSelectQuery = sqlQueryExpr.getSubQuery().getQuery();
            setSQLSchema(sqlSelectQuery);
        }
        if (sqlExpr instanceof SQLBinaryOpExpr) {
            SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) sqlExpr;
            setSQLSchema(sqlBinaryOpExpr);
        }
        if (sqlExpr instanceof SQLMethodInvokeExpr) {
            SQLMethodInvokeExpr expr = (SQLMethodInvokeExpr) sqlExpr;
            List<SQLExpr> arguments = expr.getArguments();
            for (SQLExpr argument : arguments) {
                setSQLSchema(argument);
            }
        }
    }

}
